package com.hzw.saas.common.storage.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.storage.config.AwsS3Config;
import com.hzw.saas.common.storage.util.FileStreamUtil;
import com.hzw.saas.common.util.IOUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service("aws")
@Lazy
@ConditionalOnProperty(name = "saas.storage.s3Service", matchIfMissing = true, havingValue = "aws")
@RequiredArgsConstructor
@Slf4j
public class AwsS3FileServiceImpl extends AbstractS3FileServiceImpl {

    private final AwsS3Config s3Config;

    private AmazonS3 ossClient;

    static int BUF_SIZE = 8192;

    /**
     * 初始化OssClient
     */
    @PostConstruct
    public void initOssClient() {
        if (null == ossClient) {
            try {
                BasicAWSCredentials awsCreds = new BasicAWSCredentials(s3Config.getAccessKey(), s3Config.getSecretKey());
                ossClient = AmazonS3ClientBuilder.standard()
                    .withRegion(s3Config.getRegion())
                    .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
                    .build();
            } catch (Exception e) {
                log.error("初始化AWS S3失败");
            }
        }
    }

    @Override
    protected String getStorageName() {
        return "aws s3";
    }

    @Override
    public String uploadFile(File file, String fileKey) throws IOException {
        return this.uploadFile(file, fileKey, s3Config.getBucket());
    }

    @Override
    public String uploadFile(File file, String fileKey, String bucket) throws IOException {
        log.debug("[{}] upload file({}) start...", this.getStorageName(), fileKey);
        if (StringUtils.isEmpty(fileKey)) {
            // key命名格式: sha-1
            fileKey = DigestUtil.sha1Hex(file);
        }
        // 上传文件数据
        ossClient.putObject(bucket, fileKey, file);
        log.debug("[{}] upload file({}) completed.", this.getStorageName(), fileKey);
        return fileKey;
    }

    @Override
    public void downloadFile(String fileKey, String tarPath) throws IOException {
        try (OutputStream outputStream = FileUtil.getOutputStream(tarPath)) {
            downloadFile(fileKey, outputStream);
        }
    }

    @Override
    public void downloadFile(String fileKey, OutputStream ous) throws IOException {
        this.downloadFile(fileKey, ous, -1);
    }

    @Override
    public void downloadFile(String fileKey, OutputStream ous, long skipBytes) throws IOException {
        log.debug("[{}] download file({}) start...", this.getStorageName(), fileKey);
        S3Object ossObject = ossClient.getObject(s3Config.getBucket(), fileKey);
        InputStream ins = ossObject.getObjectContent();
        IOUtil.skip(ins, skipBytes);
        FileStreamUtil.pipeStream(ins, ous);
        // IoUtil.copy(ins, ous);
        log.debug("[{}] download file({}) completed.", this.getStorageName(), fileKey);
    }

    @Override
    public boolean exists(String fileKey) {
        return this.exists(fileKey, s3Config.getBucket());
    }

    @Override
    public boolean exists(String key, String bucket) {
        boolean exist = ossClient.doesObjectExist(bucket, key);
        log.debug("[{}] check file({}) exists: {}", this.getStorageName(), key, exist);
        return exist;
    }

    @Override
    public List<String> getAllFileByDir(String dir) {
        log.debug("[{}] get files in folder({})...", this.getStorageName(), dir);
        dir = dealDir(dir);
        List<String> keys = new ArrayList<>();
        String nextMarker = null;
        ObjectListing objectListing;
        do {
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest()
                .withBucketName(s3Config.getBucket())
                .withPrefix(dir)
                .withMarker(nextMarker);
            objectListing = ossClient.listObjects(listObjectsRequest);
            List<S3ObjectSummary> sums = objectListing.getObjectSummaries();
            keys.addAll(sums.stream().map(S3ObjectSummary::getKey).collect(Collectors.toList()));
            nextMarker = objectListing.getNextMarker();
        } while (objectListing.isTruncated());

        if (!CollectionUtils.isEmpty(keys)) {
            // 去除本身
            keys.remove(0);
        }
        return keys;
    }

    @Override
    public void deleteFile(String key) {
        log.debug("[{}] delete file({})...", this.getStorageName(), key);
        ossClient.deleteObject(s3Config.getBucket(), key);
    }

    @Override
    public void deleteFiles(List<String> keys) {
        log.debug("[{}] delete files({})", this.getStorageName(), JSONUtil.toJsonStr(keys));
        if (CollectionUtils.isEmpty(keys)) {
            return;
        }
        ossClient.deleteObjects(new DeleteObjectsRequest(s3Config.getBucket())
            .withKeys(keys.toArray(new String[]{})));
    }

    @Override
    public void createDir(String dir) throws IOException {
        log.debug("[{}] create folder({})...", this.getStorageName(), dir);
        dir = dealDir(dir);
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(0);
        InputStream emptyContent = new ByteArrayInputStream(new byte[0]);
        PutObjectRequest putObjectRequest = new PutObjectRequest(s3Config.getBucket(), dir, emptyContent, metadata);
        ossClient.putObject(putObjectRequest);
    }

    @Override
    public void deleteDir(String dir) throws IOException {
        log.debug("[{}] delete folder({})...", this.getStorageName(), dir);
        // 判断目录是否为空
        AssertUtil.assertThrow("目录删除失败，请先删除目录下的所有文件",
            !CollectionUtils.isEmpty(getAllFileByDir(dir)));
        deleteFile(dir);
    }

    @Override
    protected void copyFile(String key, String newKey) {
        log.debug("[{}] copy file({} -> {})...", this.getStorageName(), key, newKey);
        CopyObjectRequest copyObjectRequest = new CopyObjectRequest(
            s3Config.getBucket(), key,
            s3Config.getBucket(), newKey);
        ossClient.copyObject(copyObjectRequest);
    }

}
